/* * SleepArchiver - cross-platform data manager for Sleeptracker-series watches. * Copyright (C) 2009-2011 Pavel Fatin <http://pavelfatin.com> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package com.pavelfatin.sleeparchiver.model; import com.csvreader.CsvReader; import com.csvreader.CsvWriter; import com.pavelfatin.sleeparchiver.lang.Utilities; import org.xml.sax.SAXException; import javax.xml.XMLConstants; import javax.xml.bind.*; import javax.xml.bind.annotation.*; import javax.xml.validation.Schema; import javax.xml.validation.SchemaFactory; import java.io.*; import java.net.URL; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.zip.GZIPInputStream; import java.util.zip.GZIPOutputStream; @XmlRootElement(name = "document") @XmlAccessorType(value = XmlAccessType.FIELD) public class Document { private static Schema _schema; @XmlAttribute(name = "version") private float _version = 1.0F; @XmlElement(name = "night") private List<Night> _nights = new ArrayList<Night>(); @XmlTransient private File _location; public Document() { } public Document(List<Night> nights) { _nights = new ArrayList<Night>(nights); } public List<Night> getNights() { return Collections.unmodifiableList(_nights); } private void setLocation(File location) { _location = location; } public static Document load(File file) throws IOException { InputStream in = new GZIPInputStream(new BufferedInputStream(new FileInputStream(file))); try { Document result = loadFrom(in); result.setLocation(file); return result; } catch (JAXBException e) { throw new RuntimeException(e); } finally { Utilities.close(in); } } static Document loadFrom(InputStream stream) throws JAXBException { Unmarshaller unmarshaller = createContext().createUnmarshaller(); unmarshaller.setEventHandler(new ValidationHandler()); unmarshaller.setSchema(getSchema()); return (Document) unmarshaller.unmarshal(stream); } private static Schema getSchema() { if (_schema == null) { _schema = loadSchema("document.xsd"); } return _schema; } private static Schema loadSchema(String file) { try { SchemaFactory sf = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); URL url = Document.class.getResource(file); if (url == null) { throw new RuntimeException("Can't find document XML schema: " + file); } return sf.newSchema(url); } catch (SAXException e) { throw new RuntimeException("Can't load document XML schema", e); } } public void save(boolean backup) throws IOException { saveAs(_location, backup); } public void saveAs(File file, boolean backup) throws IOException { byte[] bytes = saveToBytes(); if (backup && file.exists()) { createBackup(file); } OutputStream out = new GZIPOutputStream(new BufferedOutputStream(new FileOutputStream(file))); try { out.write(bytes); out.flush(); setLocation(file); } finally { Utilities.close(out); } } private byte[] saveToBytes() { ByteArrayOutputStream buffer = new ByteArrayOutputStream(); try { saveTo(buffer); return buffer.toByteArray(); } catch (PropertyException e) { throw new RuntimeException(e); } catch (JAXBException e) { throw new RuntimeException(e); } finally { Utilities.close(buffer); } } void saveTo(OutputStream stream) throws JAXBException { Marshaller marshaller = createContext().createMarshaller(); marshaller.setEventHandler(new ValidationHandler()); marshaller.setSchema(getSchema()); marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); marshaller.setProperty(Marshaller.JAXB_SCHEMA_LOCATION, "http://pavelfatin.com/sleeparchiver http://pavelfatin.com/files/sleeparchiver/document.xsd"); marshaller.marshal(this, stream); } private void createBackup(File file) throws IOException { File backup = new File(file.getPath() + ".bak"); if (backup.exists()) { if (!backup.delete()) { throw new IOException( "Unable to rename file " + backup.getPath()); } } if (!file.renameTo(backup)) { throw new IOException( "Unable to rename file " + file.getPath() + " to " + backup.getPath()); } } private static JAXBContext createContext() throws JAXBException { return JAXBContext.newInstance(Document.class, Night.class, Date.class, Time.class); } public static List<Night> importData(File file) throws IOException { List<Night> nights; BufferedReader reader = new BufferedReader(new FileReader(file)); try { nights = importDataFrom(reader); } finally { Utilities.close(reader); } return nights; } static List<Night> importDataFrom(BufferedReader reader) throws IOException { List<Night> nights = new ArrayList<Night>(); while (reader.ready()) { String line = reader.readLine(); if (line == null) { break; } if (line.trim().length() == 0) { continue; } nights.add(parse(line)); } Collections.sort(nights, Night.getComparator()); return nights; } private static Night parse(String line) { List<String> columns = split(line); List<Time> moments = new ArrayList<Time>(); List<String> conditions = new ArrayList<String>(); boolean momentsColumns = true; for (int i = 9; i < columns.size(); i++) { String column = columns.get(i); if (column.isEmpty()) { momentsColumns = false; continue; } if (momentsColumns) { moments.add(Time.parse(column)); } else { conditions.add(column); } } return new Night(Date.parse(columns.get(0)), Time.parse(columns.get(1)), Integer.parseInt(columns.get(2)), Time.parse(columns.get(3)), Ease.parse(columns.get(4)), Quality.parse(columns.get(5)), Ease.parse(columns.get(6)), "true".equalsIgnoreCase(columns.get(7)), columns.get(8), moments, conditions); } public static void exportData(File file, List<Night> nights) throws IOException { BufferedWriter writer = new BufferedWriter(new FileWriter(file)); try { exportDataTo(writer, nights); writer.flush(); } finally { Utilities.close(writer); } } static void exportDataTo(BufferedWriter writer, List<Night> nights) throws IOException { int maxMomentsCount = getMaxMomentsCount(nights); for (Night night : nights) { List<String> columns = new ArrayList<String>(); columns.add(Date.format(night.getDate())); columns.add(Time.format(night.getAlarm())); columns.add(Integer.toString(night.getWindow())); columns.add(Time.format(night.getToBed())); columns.add(night.getEaseOfFallingAsleep().format()); columns.add(night.getQualityOfSleep().format()); columns.add(night.getEaseOfWakingUp().format()); columns.add(night.isAlarmWorked() ? "true" : "false"); columns.add(night.getComments()); for (Time moment : night.getMoments()) { columns.add(Time.format(moment)); } if (night.hasConditions()) { int levelers = maxMomentsCount - night.getMomentsCount(); columns.addAll(Collections.nCopies(levelers + 1, "")); columns.addAll(night.getConditions()); } String line = join(columns); writer.write(line); writer.newLine(); } } public static int getMaxMomentsCount(List<Night> nights) { int result = 0; for (Night night : nights) { if (night.getMomentsCount() > result) { result = night.getMomentsCount(); } } return result; } public boolean isNew() { return _location == null; } public File getLocation() { return _location; } public String getName() { return _location == null ? "" : _location.getName().replaceAll("\\.\\S{3}$", ""); } static String join(List<String> columns) { try { StringWriter buffer = new StringWriter(); CsvWriter writer = new CsvWriter(buffer, ';'); writer.writeRecord(columns.toArray(new String[]{})); writer.close(); return buffer.toString().trim().replaceAll("\r", "\\\\r").replaceAll("\n", "\\\\n"); } catch (IOException e) { throw new RuntimeException(e); } } static List<String> split(String line) { try { CsvReader reader = new CsvReader(new StringReader(line), ';'); reader.readRecord(); List<String> columns = new ArrayList<String>(); for (String token : reader.getValues()) { columns.add(token.replaceAll("\\\\r", "\r").replaceAll("\\\\n", "\n")); } reader.close(); return columns; } catch (IOException e) { throw new RuntimeException(e); } } }